﻿// -----------------------------------------------------------------------
// <copyright file="BackupDto.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using System.Xml.Schema;

namespace Centido.Core.Dto
{
    /// <summary>
    ///   The data transfer object for a backup.
    /// </summary>
    public class BackupDto : DataTransferObject
    {
        /// <summary>
        ///   The name of the xml tag that is used to store this object.
        /// </summary>
        public const string XmlTag = "backup";

        /// <summary>
        ///   The current xml schema version.
        /// </summary>
        public const string CurrentXmlSchemaVersion = "1";

        /// <summary>
        ///   The name of the xml attribute to store the backup creation date and time.
        /// </summary>
        public const string CreatedOnAttribute = "createdOn";

        /// <summary>
        ///   The name of the xml attribute to store the backup creator name.
        /// </summary>
        public const string CreatedByAttribute = "createdBy";

        /// <summary>
        ///   The name of the xml tag to store the user list.
        /// </summary>
        public const string UserListTag = "users";

        /// <summary>
        ///   The name of the xml tag to store the unit list.
        /// </summary>
        public const string UnitListTag = "units";

        /// <summary>
        ///   The name of the xml tag to store the role list.
        /// </summary>
        public const string RoleListTag = "roles";

        /// <summary>
        ///   The name of the xml tag to store the table list.
        /// </summary>
        public const string TableListTag = "tables";


        /// <summary>
        ///   The user list.
        /// </summary>
        private readonly List<UserDto> _users = new List<UserDto>();
 
        /// <summary>
        ///   The unit list.
        /// </summary>
        private readonly List<UnitDto> _units = new List<UnitDto>();

        /// <summary>
        ///   The role list.
        /// </summary>
        private readonly List<RoleDto> _roles = new List<RoleDto>();

        /// <summary>
        ///   The table list.
        /// </summary>
        private readonly List<TableDto> _tables = new List<TableDto>();


        /// <summary>
        ///   Gets or sets the backup creation date and time.
        /// </summary>
        public DateTime? CreatedOn { get; set; }


        /// <summary>
        ///   Gets or sets the backup creator name.
        /// </summary>
        public string CreatedBy { get; set; }


        /// <summary>
        ///   Gets or sets the domain options.
        /// </summary>
        public DomainOptionsDto Options { get; set; }


        /// <summary>
        ///   Gets the domain tables.
        /// </summary>
        public ICollection<TableDto> Tables
        {
            get { return _tables; }
        }


        /// <summary>
        ///   Adds the specified table to the list of the domain tables.
        /// </summary>
        /// <param name="table">The table to added.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        public void AddTable(TableDto table)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            if (!_tables.Contains(table))
                _tables.Add(table);
        }


        /// <summary>
        ///   Deletes the specified table from the list of the domain tables.
        /// </summary>
        /// <param name="table">The table to deleted from the list.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        public void DeleteTable(TableDto table)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            if (_tables.Contains(table))
                _tables.Remove(table);
        }


        /// <summary>
        ///   Gets the list of the domain users.
        /// </summary>
        public ICollection<UserDto> Users
        {
            get { return _users; }
        }


        /// <summary>
        ///   Adds the specified user to the list of the domain users.
        /// </summary>
        /// <param name="user">The user to be added to the list.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="user"/> is null.</exception>
        public void AddUser(UserDto user)
        {
            if (user == null)
                throw new ArgumentNullException("user");

            if (!_users.Contains(user))
                _users.Add(user);
        }


        /// <summary>
        ///   Deletes the specified user from the domain user list.
        /// </summary>
        /// <param name="user">The user to be deleted from the list.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="user"/> is null.</exception>
        public void DeleteUser(UserDto user)
        {
            if (user == null)
                throw new ArgumentNullException("user");

            if (_users.Contains(user))
                _users.Remove(user);
        }


        /// <summary>
        ///   Gets the domain units.
        /// </summary>
        public ICollection<UnitDto> Units
        {
            get { return _units; }
        }


        /// <summary>
        ///   Adds the specified unit to the list of domain units.
        /// </summary>
        /// <param name="unit">The unit to be added to the list.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="unit"/> is null.</exception>
        public void AddUnit(UnitDto unit)
        {
            if (unit == null)
                throw new ArgumentNullException("unit");

            if (!_units.Contains(unit))
                _units.Add(unit);
        }


        /// <summary>
        ///   Deletes the specified unit from the list of domain units.
        /// </summary>
        /// <param name="unit">The unit to be deleted from the list.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="unit"/> is null.</exception>
        public void DeleteUnit(UnitDto unit)
        {
            if (unit == null)
                throw new ArgumentNullException("unit");

            if (_units.Contains(unit))
                _units.Remove(unit);
        }


        /// <summary>
        ///   Gets the domain roles.
        /// </summary>
        public ICollection<RoleDto> Roles
        {
            get { return _roles; }
        }


        /// <summary>
        ///   Adds the specified security role to the list of domain roles.
        /// </summary>
        /// <param name="role">The security role to be added to the list.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="role"/> is null.</exception>
        public void AddRole(RoleDto role)
        {
            if (role == null)
                throw new ArgumentNullException("role");

            if (!_roles.Contains(role))
                _roles.Add(role);
        }


        /// <summary>
        ///   Deletes the specified security role from the list of domain roles.
        /// </summary>
        /// <param name="role">The security role to be deleted from the list.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="role"/> is null.</exception>
        public void DeleteRole(RoleDto role)
        {
            if (role == null)
                throw new ArgumentNullException("role");

            if (_roles.Contains(role))
                _roles.Remove(role);
        }


        /// <summary>
        ///   This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        ///   An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        public override XmlSchema GetSchema()
        {
            return null;
        }


        /// <summary>
        ///   Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">
        ///   The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="reader"/> is null.</exception>
        public override void ReadXml(XmlReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            reader.MoveToContent();

            // Reading the xml schema version
            var xmlSchemaVersion = ReadStringAttribute(reader, XmlSchemaVersionTag, isRequired: false);

            // If the xml schema version is not defined
            if (string.IsNullOrEmpty(xmlSchemaVersion)
                // or if it is the current xml schema version
                || xmlSchemaVersion == CurrentXmlSchemaVersion)
            {
                // Reading the user that created the backup
                CreatedBy = ReadStringAttribute(reader, CreatedByAttribute, isRequired: false);

                // Reading the backup creation data and time
                CreatedOn = ReadDateTimeAttribute(reader, CreatedOnAttribute, isRequired: false);


                // Clearing the table list
                _tables.Clear();

                // Clearing the unit list
                _units.Clear();

                // Clearing the role list
                _roles.Clear();

                // Clearing the user list
                _users.Clear();

                // Clearing the options
                Options = null;

                var isEmptyElement = reader.IsEmptyElement;

                reader.ReadStartElement();

                if (!isEmptyElement)
                {
                    // Reading tables, units, roles, users, options, table data references
                    while (reader.LocalName == TableListTag
                        || reader.LocalName == UnitListTag
                        || reader.LocalName == RoleListTag
                        || reader.LocalName == UserListTag
                        || reader.LocalName == DomainOptionsDto.XmlTag)
                    {
                        // If this is a list of elements
                        if (reader.LocalName == TableListTag
                        || reader.LocalName == UnitListTag
                        || reader.LocalName == RoleListTag
                        || reader.LocalName == UserListTag)
                        {
                            var currentXmlTag = reader.LocalName;

                            isEmptyElement = reader.IsEmptyElement;

                            reader.ReadStartElement();

                            if (isEmptyElement) continue;

                            switch (currentXmlTag)
                            {
                                // The table list
                                case TableListTag:
                                    while (reader.LocalName == TableDto.XmlTag)
                                    {
                                        var tableDto = new TableDto();

                                        tableDto.ReadXml(reader);

                                        AddTable(tableDto);
                                    }
                                    break;
                                // The unit list
                                case UnitListTag:
                                    while (reader.LocalName == UnitDto.XmlTag)
                                    {
                                        var unitDto = new UnitDto();

                                        unitDto.ReadXml(reader);

                                        AddUnit(unitDto);
                                    }
                                    break;
                                // The role list
                                case RoleListTag:
                                    while (reader.LocalName == RoleDto.XmlTag)
                                    {
                                        var roleDto = new RoleDto();

                                        roleDto.ReadXml(reader);

                                        AddRole(roleDto);
                                    }
                                    break;
                                // The user list
                                case UserListTag:
                                    while (reader.LocalName == UserDto.XmlTag)
                                    {
                                        var userDto = new UserDto();

                                        userDto.ReadXml(reader);

                                        AddUser(userDto);
                                    }
                                    break;
                            }

                            reader.ReadEndElement();
                        }
                        // If this a single element
                        else
                        {
                            switch (reader.LocalName)
                            {
                                // The domain options
                                case DomainOptionsDto.XmlTag:
                                    var domainOptionsDto = new DomainOptionsDto();

                                    domainOptionsDto.ReadXml(reader);

                                    Options = domainOptionsDto;
                                    break;
                            }
                        }
                    }

                    reader.ReadEndElement();
                }
            }
            else
                throw new ArgumentException(string.Format(
                    "The specified xml schema version is not supported (node = {0}, version = {1}).",
                    reader.Name,
                    xmlSchemaVersion));
        }


        /// <summary>
        ///   Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">
        ///   The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="writer"/> is null.</exception>
        public override void WriteXml(XmlWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteStartElement(XmlTag);

            // The xml schema version
            writer.WriteAttributeString(XmlSchemaVersionTag, CurrentXmlSchemaVersion);

            // The user that created the backup
            if (!string.IsNullOrEmpty(CreatedBy))
                writer.WriteAttributeString(CreatedByAttribute, CreatedBy);

            // The backup creation date and time
            if (CreatedOn.HasValue)
                writer.WriteAttributeString(CreatedOnAttribute,
                    CreatedOn.Value.ToString(CultureInfo.InvariantCulture));

            // The table list
            if (_tables.Count > 0)
            {
                writer.WriteStartElement(TableListTag);

                foreach (var table in _tables)
                    table.WriteXml(writer);

                writer.WriteEndElement();
            }

            // The user list
            if (_users.Count > 0)
            {
                writer.WriteStartElement(UserListTag);

                foreach (var user in _users)
                    user.WriteXml(writer);

                writer.WriteEndElement();
            }

            // The role list
            if (_roles.Count > 0)
            {
                writer.WriteStartElement(RoleListTag);

                foreach (var role in _roles)
                    role.WriteXml(writer);

                writer.WriteEndElement();
            }

            // The unit list
            if (_units.Count > 0)
            {
                writer.WriteStartElement(UnitListTag);

                foreach (var unit in _units)
                    unit.WriteXml(writer);

                writer.WriteEndElement();
            }

            // The domain options
            if (Options != null)
                Options.WriteXml(writer);

            writer.WriteEndElement();
        }
    }
}
